﻿/*
* Copyright (c) 2010-2012 Tesla Engine Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

using System;
using System.IO;

namespace Tesla.Content {
    /// <summary>
    /// External writer used by a savable writer to write an ISavable to an external
    /// output (e.g. a secondary file).
    /// </summary>
    public abstract class ExternalWriter {
        private Type _targetType;
        private String _defaultOutputName;
        private ISavableWriter _parent;
        private String _outputDirectory;
        private bool _overwrite;

        /// <summary>
        /// Gets the target type of the external reference.
        /// </summary>
        public Type TargetType {
            get {
                return _targetType;
            }
        }

        /// <summary>
        /// Gets or sets the default output name.
        /// </summary>
        public String DefaultOutputName {
            get {
                return _defaultOutputName;
            }
            set {
                if(value == null) {
                    value = String.Empty;
                }
                _defaultOutputName = value;
            }
        }

        /// <summary>
        /// Gets the parent ISavableWriter that initialized this external writer.
        /// </summary>
        protected ISavableWriter ParentWriter {
            get {
                return _parent;
            }
        }

        /// <summary>
        /// Gets or sets the output directory, relative to the primary resource name.
        /// </summary>
        public String OutputDirectory {
            get {
                return _outputDirectory;
            }
            set {
                if(value == null) {
                    value = String.Empty;
                }
                _outputDirectory = value;
            }
        }

        /// <summary>
        /// Gets or sets if the writer should overwrite existing files. Default is true.
        /// </summary>
        public bool Overwrite {
            get {
                return _overwrite;
            }
            set {
                _overwrite = value;
            }
        }

        /// <summary>
        /// Creates a new ExternalWriter.
        /// </summary>
        /// <param name="targetType">Savable type the writer serializes</param>
        /// <param name="defaultOutputName">Default output name</param>
        /// <param name="outputDirectory">Output directory, relative to the primary savable output.</param>
        protected ExternalWriter(Type targetType, String defaultOutputName, String outputDirectory) {
            _targetType = targetType;
            _overwrite = true;
            DefaultOutputName = defaultOutputName;
            OutputDirectory = outputDirectory;
        }

        /// <summary>
        /// Initializes the writer. Called when the writer is added to the the savable writer.
        /// </summary>
        /// <param name="parentWriter">Parent savable writer</param>
        internal void Initialize(ISavableWriter parentWriter) {
            _parent = parentWriter;
            OnInitialize();
        }

        /// <summary>
        /// Called when the writer is initialized by the savable writer, when it is registered with the savable
        /// writer.
        /// </summary>
        protected virtual void OnInitialize() { }

        /// <summary>
        /// Gets the file directory: Directory of FullPrimaryResourceName + OutputDirectory.
        /// </summary>
        /// <returns>File directory</returns>
        protected String GetFileDirectory() {
            String fileDir = Path.GetDirectoryName(_parent.FullResourceName);
            if(!String.IsNullOrEmpty(OutputDirectory)) {
                fileDir = Path.Combine(fileDir, OutputDirectory);
            }
            return fileDir;
        }

        /// <summary>
        /// Gets the primary resource file name without extension.
        /// </summary>
        /// <returns>Resource name that is relative to the writer's output.</returns>
        protected String GetPrimaryResourceName() {
            String resourceName = Path.GetFileNameWithoutExtension(_parent.FullResourceName);
            return resourceName;
        }

        /// <summary>
        /// Cleans up any remaining resources and finishes writing external objects.
        /// </summary>
        public abstract void Flush();

        /// <summary>
        /// Writes the external object and returns the reference to it.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">External savable to write</param>
        /// <param name="type">Savable type</param>
        /// <returns>The external reference</returns>
        public abstract ExternalReference Write(String name, ISavable value, Type type);
    }

    /// <summary>
    /// External strongly-typed writer used by a savable writer to write an ISavable to an external
    /// output (e.g. a secondary file).
    /// </summary>
    public abstract class ExternalWriter<T> : ExternalWriter where T : ISavable {

        /// <summary>
        /// Creates a new ExternalWriter.
        /// </summary>
        /// <param name="defaultOutputName">Default name of the output.</param>
        /// <param name="outputDirectory">The output directory.</param>
        protected ExternalWriter(String defaultOutputName, String outputDirectory) : base(typeof(T), defaultOutputName, outputDirectory) { }

        /// <summary>
        /// Writes the external object and returns the reference to it.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">External savable to write</param>
        /// <param name="type">Savable type</param>
        /// <returns>The external reference</returns>
        public override ExternalReference Write(String name, ISavable value, Type type) {
            if(value == null) {
                throw new ArgumentNullException("Savable cannot be null.");
            }
            if(!TargetType.IsAssignableFrom(type)) {
                throw new InvalidCastException(String.Format("Invalid type, cannot load specified type: {0}, expected type: {1}.", type.FullName, TargetType.FullName));
            }

            return Write(name, (T) value);
        }

        /// <summary>
        /// Writes the external object and returns the reference to it.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">External object to write</param>
        /// <returns>The external reference</returns>
        public abstract ExternalReference Write(String name, T value);
    }
}
